using System;
using System.Text;
using Microsoft.SqlServer.Dts.Pipeline;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
using System.Windows.Forms;
using System.IO;

namespace Jamiet.Ssis
{
    [DtsPipelineComponent(
        DisplayName = "Normaliser",
        Description = "Splits sorted input into header and detail records",
        ComponentType = ComponentType.Transform,
        NoEditor = true,
        IconResource = "Jamiet.Ssis.NormaliserComponent.Normaliser.ico"
        )
    ]
    public class Normaliser : PipelineComponent
    {
        #region design-time
        public override void ProvideComponentProperties()
        {
            this.RemoveAllInputsOutputsAndCustomProperties();
            #region Add input
            IDTSInput100 input = ComponentMetaData.InputCollection.New();
            input.Name = "Sorted master and detail data";
            #endregion
            #region Add 2 outputs
            IDTSOutput100 master = ComponentMetaData.OutputCollection.New();
            master.Name = "Master";
            master.IsSorted = true;
            //master.Dangling = true; //The output doesnt necassarily have to be attached to anything.
            master.Description = "All master records";
            master.SynchronousInputID = 0;
            IDTSOutput100 detail = ComponentMetaData.OutputCollection.New();
            detail.Name = "Detail";
            detail.IsSorted = true;
            //detail.Dangling = true; //The output doesnt necassarily have to be attached to anything.
            detail.Description = "All detail records";
            detail.SynchronousInputID = 0;
            #endregion
            ComponentMetaData.UsesDispositions = false;
            ComponentMetaData.ContactInfo = "Jamie Thomson. Type my name into a search engine and I will most likely pop up somwhere :) !";
            //No custom properties
            //No connection requirements
        }

        public override void ReinitializeMetaData()
        {
            /*
             * Called when Refresh button is pushed in Advanced UI or when Validate() returns VS_NEEDSNEWMETADATA.
             * */
            #region Initialize
            base.ReinitializeMetaData();
            //remove all columns
            ComponentMetaData.OutputCollection["Master"].OutputColumnCollection.RemoveAll();
            ComponentMetaData.OutputCollection["Detail"].OutputColumnCollection.RemoveAll();
            #endregion


            IDTSOutput100 master = ComponentMetaData.OutputCollection["Master"];
            IDTSOutput100 detail = ComponentMetaData.OutputCollection["Detail"];
            IDTSInput100 input = ComponentMetaData.InputCollection[0];

            #region Link Columns
            //Put linker field onto each output, include SortOrder
            IDTSOutputColumn100 masterLinkColumn = master.OutputColumnCollection.New();
            masterLinkColumn.Name = "LinkID";
            masterLinkColumn.SetDataTypeProperties(DataType.DT_I4, 0, 0, 0, 0);
            masterLinkColumn.SortKeyPosition = 1;
            IDTSOutputColumn100 detailLinkColumn = detail.OutputColumnCollection.New();
            detailLinkColumn.Name = "LinkID";
            detailLinkColumn.SetDataTypeProperties(DataType.DT_I4, 0, 0, 0, 0);
            detailLinkColumn.SortKeyPosition = 1;
            #endregion

            //add all columns from upstream virtual input, include SortKey
            foreach (IDTSVirtualInputColumn100 inCol in input.GetVirtualInput().VirtualInputColumnCollection)
            {
                if (inCol.SortKeyPosition > 0)
                {
                    //if input column is sorted add it to master output
                    IDTSOutputColumn100 masterOutCol = master.OutputColumnCollection.New();
                    masterOutCol.Name = inCol.Name;
                    masterOutCol.SetDataTypeProperties(inCol.DataType, inCol.Length, inCol.Precision, inCol.Scale, inCol.CodePage);
                    masterOutCol.Description = inCol.Description;
                    masterOutCol.SortKeyPosition = inCol.SortKeyPosition + 1; //the link column has sortkey=1
                }
                else
                {
                    //add it to detail
                    IDTSOutputColumn100 detailOutCol = detail.OutputColumnCollection.New();
                    detailOutCol.Name = inCol.Name;
                    detailOutCol.SetDataTypeProperties(inCol.DataType, inCol.Length, inCol.Precision, inCol.Scale, inCol.CodePage);
                    detailOutCol.Description = inCol.Description;
                    detailOutCol.SortKeyPosition = 0;
                }
            }
        }

        public override DTSValidationStatus Validate()
        {
            //Always call the base Validate()
            DTSValidationStatus status = base.Validate();
            if (status == DTSValidationStatus.VS_ISCORRUPT)
            {
                return status;
            }

            //check there is one input. if not VS_ISCORRUPT. Throw an error.
            //check virtualinput is sorted
            IDTSInput100 input = ComponentMetaData.InputCollection[0];
            IDTSOutput100 master = ComponentMetaData.OutputCollection["Master"];

            if (!input.IsAttached)
            {
                ThrowException("This component uses information from an upstream component to build its outputs. You must attach an input.");
                status = DTSValidationStatus.VS_ISBROKEN;
            }
            else if (input.GetVirtualInput().IsSorted == false)
            {
                ThrowException("The input needs to be sorted.");
                status = DTSValidationStatus.VS_ISBROKEN;
            }
            else if (master.OutputColumnCollection.Count == 0)
            {
                //no columns have been added yet so RMD
                status = DTSValidationStatus.VS_NEEDSNEWMETADATA;
            }
            else if (!CheckInputAndOutputsMatch()) //check to see if input matches the outputs. names, metadata etc...
            {
                status = DTSValidationStatus.VS_NEEDSNEWMETADATA;
            }
            else
            {
                //do nothing. status is whatever was returned from base.Validate()
            }
            return status;
        }

        public override void OnInputPathAttached(int inputID)
        {
            base.OnInputPathAttached(inputID);
            this.ReinitializeMetaData();
        }
        #endregion

        #region execution-time
        private ColumnInfo[] inputColumnInfos;
        struct ColumnInfo
        {
            public int inputBufferIndex;
            public string Name;
            public bool isMaster;
            public int outputBufferIndex;
            public DataType dataType;
        }

        public override void PreExecute()
        {
            IDTSInput100 input = ComponentMetaData.InputCollection[0];
            IDTSVirtualInput100 vInput = input.GetVirtualInput();
            inputColumnInfos = new ColumnInfo[vInput.VirtualInputColumnCollection.Count];

            for (int x = 0; x < vInput.VirtualInputColumnCollection.Count; x++)
            {
                ColumnInfo colInfo = new ColumnInfo();

                //colInfo.bufferIndex = BufferManager.FindColumnByLineageID( input.Buffer, input.InputColumnCollection[x].LineageID);
                //colInfo.Name = input.InputColumnCollection[x].Name;
                colInfo.inputBufferIndex = BufferManager.FindColumnByLineageID(input.Buffer, vInput.VirtualInputColumnCollection[x].LineageID);
                colInfo.Name = vInput.VirtualInputColumnCollection[x].Name;
                colInfo.dataType = vInput.VirtualInputColumnCollection[x].DataType;
                if (vInput.VirtualInputColumnCollection[x].SortKeyPosition > 0)
                {
                    colInfo.isMaster = true;
                }
                else
                {
                    colInfo.isMaster = false;
                }
                inputColumnInfos[x] = colInfo;
            }
        }

        private PipelineBuffer masterBuffer;
        private PipelineBuffer detailBuffer;
        public override void PrimeOutput(int outputs, int[] outputIDs, PipelineBuffer[] buffers)
        {
            masterBuffer = buffers[0];
            detailBuffer = buffers[1];

            //work out which output column each input column is mapped to
            for (int i = 0; i < inputColumnInfos.Length; i++)
            {
                if (inputColumnInfos[i].isMaster)
                {
                    IDTSOutput100 masterOutput = ComponentMetaData.OutputCollection["Master"];
                    for (int x = 1; x < masterOutput.OutputColumnCollection.Count; x++) //No need to loop over first output column because that is the LinkID
                    {
                        if (masterOutput.OutputColumnCollection[x].Name == inputColumnInfos[i].Name)
                        {
                            inputColumnInfos[i].outputBufferIndex = BufferManager.FindColumnByLineageID(masterOutput.Buffer, masterOutput.OutputColumnCollection[x].LineageID);
                            break;
                        }
                    }
                }
                else
                {
                    IDTSOutput100 detailOutput = ComponentMetaData.OutputCollection["Detail"];
                    for (int x = 1; x < detailOutput.OutputColumnCollection.Count; x++) //No need to loop over first output column because that is the LinkID
                    {
                        if (detailOutput.OutputColumnCollection[x].Name == inputColumnInfos[i].Name)
                        {
                            inputColumnInfos[i].outputBufferIndex = BufferManager.FindColumnByLineageID(detailOutput.Buffer, detailOutput.OutputColumnCollection[x].LineageID);
                            break;
                        }
                    }
                }
            }
        }

        private int sortColsHash;
        private int LinkID;
        public override void ProcessInput(int inputID, PipelineBuffer buffer)
        {
            if (!buffer.EndOfRowset)
            {
                while (buffer.NextRow())
                {
                    //Build hash
                    StringBuilder str = new StringBuilder();
                    for (int x = 0; x < inputColumnInfos.Length; x++)
                    {
                        ColumnInfo colInfo = inputColumnInfos[x];
                        if (inputColumnInfos[x].isMaster)
                        {
                            //allow for input column being NULL
                            /*****Code commented out on 29th April 2009********
                            string colValue = buffer[colInfo.inputBufferIndex].ToString();
                            str.Append(colValue == string.Empty ? "~Nu1L r3pl@cEmeNT~" : colValue);
                             */
                            if (buffer.IsNull(colInfo.inputBufferIndex))
                                str.Append("~Nu1L r3pl@cEmeNT~");
                            else
                                str.Append(buffer[colInfo.inputBufferIndex].ToString());
                        }
                    }
                    int currentRowHash = str.ToString().GetHashCode();

                    //Add a new master record if one is required
                    if (sortColsHash != currentRowHash)
                    {
                        LinkID++;
                        sortColsHash = currentRowHash;
                        masterBuffer.AddRow();
                        masterBuffer.SetInt32(0, LinkID);
                        for (int i = 0; i < inputColumnInfos.Length; i++)
                        {
                            if (inputColumnInfos[i].isMaster)
                            {
                                //AddValueToBuffer(buffer, masterBuffer, inputColumnInfos[i]);
                                AddValueToBuffer(masterBuffer, inputColumnInfos[i].outputBufferIndex, buffer[inputColumnInfos[i].inputBufferIndex], inputColumnInfos[i].dataType);
                            }
                        }
                    }

                    //Add a new detail record
                    detailBuffer.AddRow();
                    detailBuffer.SetInt32(0, LinkID);
                    for (int i = 0; i < inputColumnInfos.Length; i++)
                    {

                        if (!inputColumnInfos[i].isMaster)
                        {
                            //AddValueToBuffer(buffer, detailBuffer, inputColumnInfos[i]);
                            AddValueToBuffer(detailBuffer, inputColumnInfos[i].outputBufferIndex, buffer[inputColumnInfos[i].inputBufferIndex], inputColumnInfos[i].dataType);
                        }
                    }
                }
            }
            else
            {
                masterBuffer.SetEndOfRowset();
                detailBuffer.SetEndOfRowset();
            }
        }

        #endregion

        #region Helper methods
        private static void AddValueToBuffer(PipelineBuffer outputBuffer, int outputBufferIndex, Object value, DataType dataType)
        {
            if (value == null)
            {
                outputBuffer.SetNull(outputBufferIndex);
            }
            else
            {
                switch (dataType)
                {
                    case DataType.DT_BOOL:
                        outputBuffer.SetBoolean(outputBufferIndex, Convert.ToBoolean(value));
                        break;
                    case DataType.DT_BYTES:
                        outputBuffer.SetBytes(outputBufferIndex, (byte[])value);
                        break;
                    case DataType.DT_CY:
                        outputBuffer.SetDecimal(outputBufferIndex, Convert.ToDecimal(value));
                        break;
                    case DataType.DT_DATE:
                        outputBuffer.SetDateTime(outputBufferIndex, Convert.ToDateTime(value));
                        break;
                    case DataType.DT_DBDATE:
                        outputBuffer[outputBufferIndex] = (DateTime)value;
                        break;
                    case DataType.DT_DBTIME:
                        //It is not possible to populate DT_DBTIME columns from managed code in SSIS2005
                        break;
                    case DataType.DT_DBTIMESTAMP:
                        outputBuffer.SetDateTime(outputBufferIndex, Convert.ToDateTime(value));
                        break;
                    case DataType.DT_DECIMAL:
                        outputBuffer.SetDecimal(outputBufferIndex, Convert.ToDecimal(value));
                        break;
                    case DataType.DT_FILETIME:
                        outputBuffer[outputBufferIndex] = value;
                        break;
                    case DataType.DT_GUID:
                        outputBuffer.SetGuid(outputBufferIndex, (Guid)value);
                        break;
                    case DataType.DT_I1:
                        outputBuffer.SetSByte(outputBufferIndex, Convert.ToSByte(value));
                        break;
                    case DataType.DT_I2:
                        outputBuffer.SetInt16(outputBufferIndex, Convert.ToInt16(value));
                        break;
                    case DataType.DT_I4:
                        outputBuffer.SetInt32(outputBufferIndex, Convert.ToInt32(value));
                        break;
                    case DataType.DT_I8:
                        outputBuffer.SetInt64(outputBufferIndex, Convert.ToInt64(value));
                        break;
                    case DataType.DT_IMAGE:
                        //We have to treat blob columns a little differently
                        BlobColumn colDT_IMAGE = (BlobColumn)value;
                        if (colDT_IMAGE.IsNull)
                        {
                            outputBuffer.SetNull(outputBufferIndex);
                        }
                        else
                        {
                            outputBuffer.AddBlobData(outputBufferIndex, colDT_IMAGE.GetBlobData(0, (int)colDT_IMAGE.Length));
                        }
                        break;
                    case DataType.DT_NTEXT:
                        //We have to treat blob columns a little differently
                        BlobColumn colDT_NTEXT = (BlobColumn)value;
                        if (colDT_NTEXT.IsNull)
                        {
                            outputBuffer.SetNull(outputBufferIndex);
                        }
                        else
                        {
                            outputBuffer.AddBlobData(outputBufferIndex, colDT_NTEXT.GetBlobData(0, (int)colDT_NTEXT.Length));
                        }
                        break;
                    case DataType.DT_NULL:
                        outputBuffer.SetNull(outputBufferIndex);
                        break;
                    case DataType.DT_NUMERIC:
                        outputBuffer.SetDecimal(outputBufferIndex, Convert.ToDecimal(value));
                        break;
                    case DataType.DT_R4:
                        outputBuffer.SetSingle(outputBufferIndex, Convert.ToSingle(value));
                        break;
                    case DataType.DT_R8:
                        outputBuffer.SetDouble(outputBufferIndex, Convert.ToDouble(value));
                        break;
                    case DataType.DT_STR:
                        outputBuffer.SetString(outputBufferIndex, value.ToString());
                        break;
                    case DataType.DT_TEXT:
                        //We have to treat blob columns a little differently
                        BlobColumn colDT_TEXT = (BlobColumn)value;
                        if (colDT_TEXT.IsNull)
                        {
                            outputBuffer.SetNull(outputBufferIndex);
                        }
                        else
                        {
                            outputBuffer.AddBlobData(outputBufferIndex, colDT_TEXT.GetBlobData(0, (int)colDT_TEXT.Length));
                        }
                        break;
                    case DataType.DT_UI1:
                        outputBuffer.SetByte(outputBufferIndex, Convert.ToByte(value));
                        break;
                    case DataType.DT_UI2:
                        outputBuffer.SetUInt16(outputBufferIndex, Convert.ToUInt16(value));
                        break;
                    case DataType.DT_UI4:
                        outputBuffer.SetUInt32(outputBufferIndex, Convert.ToUInt32(value));
                        break;
                    case DataType.DT_UI8:
                        outputBuffer.SetUInt64(outputBufferIndex, Convert.ToUInt64(value));
                        break;
                    case DataType.DT_WSTR:
                        outputBuffer.SetString(outputBufferIndex, value.ToString());
                        break;
                    default:
                        break;
                }
            }
        }

        private bool CheckInputAndOutputsMatch()
        {
            bool valid = true;
            IDTSInput100 input = ComponentMetaData.InputCollection[0];
            IDTSOutput100 master = ComponentMetaData.OutputCollection["Master"];
            IDTSOutput100 detail = ComponentMetaData.OutputCollection["Detail"];

            //check number of columns is the same
            if (input.GetVirtualInput().VirtualInputColumnCollection.Count + 2 !=
                    master.OutputColumnCollection.Count + detail.OutputColumnCollection.Count)
            {
                valid = false;
            }
            else
            {
                //check column metadata is the same
                foreach (IDTSVirtualInputColumn100 incol in input.GetVirtualInput().VirtualInputColumnCollection)
                {
                    if (incol.SortKeyPosition > 0)
                    {
                        valid = CheckInputColumnAndOutputColumnMetadataMatch(incol, master);
                    }
                    else
                    {
                        valid = CheckInputColumnAndOutputColumnMetadataMatch(incol, detail);
                    }
                    if (!valid) break; //if we've found an invalid column, stop looping
                }
            }
            return valid;
        }

        private static bool CheckInputColumnAndOutputColumnMetadataMatch(IDTSVirtualInputColumn100 incol, IDTSOutput100 output)
        {
            //Loop over the columns to make sure a column exists with the same name
            bool metadataMatchFound = false;
            foreach (IDTSOutputColumn100 outCol in output.OutputColumnCollection)
            {
                if (outCol.Name == incol.Name)
                {
                    //Check that metadata of the two columns is the same
                    if (incol.Length == outCol.Length && incol.Precision == outCol.Precision && incol.DataType == outCol.DataType
                            && incol.Scale == outCol.Scale && incol.CodePage == outCol.CodePage)
                    {
                        metadataMatchFound = true;
                    }
                    break; //We've found a name match so no need to loop anymore
                }
            }
            return metadataMatchFound;
        }
        #endregion

        #region housekeeping
        public override IDTSInput100 InsertInput(DTSInsertPlacement insertPlacement, int inputID)
        {
            ThrowException("You cannot insert an input (" + inputID.ToString() + ")");
            return null;
        }

        public override IDTSOutput100 InsertOutput(DTSInsertPlacement insertPlacement, int outputID)
        {
            ThrowException("You cannot insert an output (" + outputID.ToString() + ")");
            return null;
        }

        public override void DeleteInput(int inputID)
        {
            ThrowException("You cannot delete an input (" + inputID.ToString() + ")");
        }

        public override void DeleteOutput(int outputID)
        {
            ThrowException("You cannot delete an output (" + outputID.ToString() + ")");
        }

        private void ThrowException(String msg)
        {
            throw new Exception(msg);
        }

        #endregion
    }
}
